Isoler un service web debian avec lxc
Article original : Isoler un service web sous Debian 9 avec LXC
Sommaire
Installation et configuration des LXC
Mise en place d’un reverse proxy
Renouvellement des certificats
Installation et configuration de la base de données
Installation et configuration du serveur web
Cet article a pour objectif de montrer quelques techniques (plus ou moins avancées) permettant une sécurisation complète d’un service web à travers l’installation d’un forum utilisant une base de données.
L’environnement utilisé sera un Debian 9 car c’est la dernière release de Debian à l’heure où j’écris l’article. Cependant les mêmes principes peuvent être appliqués à n’importe quelle distribution, et en plus de cela, vous serez en mesure de réutiliser les commandes que je fournis sur tous les dérivés de Debian (Ubuntu, Linux Mint, …).
Voici en un petit schéma, ce que nous allons faire dans cet article :
Une bonne pratique de sécurité consiste à isoler les différents services présents sur votre serveur. Pour faire cela, nous allons utiliser des LinuX Containers.
LXC, contraction de l’anglais Linux Containers est un système de virtualisation, utilisant l’isolation comme méthode de cloisonnement au niveau du système d’exploitation. Il est utilisé pour faire fonctionner des environnements Linux isolés les uns des autres dans des conteneurs partageant le même noyau et une plus ou moins grande partie du système hôte. Le conteneur apporte une limitation et une gestion de la priorisation de l’environnement d’exécution (processeur, mémoire vive, réseau, système de fichier…) et non pas de la machine. Pour cette raison, on parle de « conteneur » et non de machine virtuelle.
Le gros avantage des LXC, c’est leur légereté. Contrairement à une VM, ceux-ci ne prennent aucune ressource sur la machine autre que les applications installées à l’intérieur. Les LXC sont donc une solution idéale pour faire de l’isolation de service le plus légèrement possible.
Mais pourquoi isoler ?
Pour la simple et bonne raison que si un pirate parvient à trouver une vulnérabilité sur votre site et à l’exploiter, il n’aura pas accès au reste du système, mais uniquement au container dans lequel le service se trouve.
Connectez vous via SSH en root sur votre machine.
Nous allons commencer par installer quelques dépendances :
apt install lxc cgmanager uidmap libpam-systemd libpam-cgfs cgroup-bin bridge-utils xz dirmngr
Ensuite, nous allons créer un utilisateur pour chaque service car nous allons créer des containers dits non-privilégiés (unprivileged in english) pour plus de sécurité. Pour l’exemple, nous allons créer un utilisateur pour notre forum et un deuxième pour la base de données :
adduser forum
adduser bdd
Notez dans un coin les uid et gid des utilisateurs que vous venez de créer :
root@witchdoctors:/root # cat /etc/subuid <(echo) /etc/subgid
forum:100000:65536
bdd:165536:65536
forum:100000:65536
bdd:165536:65536
Les 2 fichiers sont très souvent identiques (si vous n’avez pas créé de groupes en fait), vous devez noter les ids en question, ici : 100000 (pour forum) et 165536 (pour bdd) qui est le début des ids qui leurs sont attribués. Si chez vous l’uid et le gid ne sont pas les mêmes, notez bien les 2 valeurs. Notez également le 65536 qui est le nombre d’ids attribués à un utilisateur.
Nous allons commencer par créer un bridge sur notre hote qui permettra par la suite de donner un accès internet à nos LXC.
Pour faire en sorte que le bridge soit créé même lorsque la machine redémarre, nous allons créer ce bridge dans le fichier /etc/network/interfaces en y ajoutant :
auto lxc-nat-bridge
iface lxc-nat-bridge inet static
bridge_ports none
bridge_fd 0
bridge_stp off
address 192.168.1.1
netmask 255.255.255.0
post-up echo 1 > /proc/sys/net/ipv4/ip_forward && echo 1 > /sys/fs/cgroup/cpuset/cgroup.clone_children && echo 1 > /proc/sys/kernel/unprivileged_userns_clone
Pour recharger la configuration du fichier interfaces, il suffit de relancer le service correspondant de cette manière :
service networking restart
En lancant la command ip a vous pouvez vérifier que le bridge a correctement été créé :
root@witchdoctors:/root # ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
inet6 ::1/128 scope host
valid_lft forever preferred_lft forever
2: eno1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
link/ether 00:1e:8c:f1:e6:a7 brd ff:ff:ff:ff:ff:ff
inet 198.245.49.61/24 brd 198.245.49.255 scope global eno1
valid_lft forever preferred_lft forever
inet6 2607:5300:60:43d::1/128 scope global
valid_lft forever preferred_lft forever
inet6 fe80::21e:8cff:fef1:e6a7/64 scope link
valid_lft forever preferred_lft forever
3: lxc-nat-bridge: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000
link/ether fe:5d:42:b7:0b:18 brd ff:ff:ff:ff:ff:ff
inet 192.168.1.1/24 brd 192.168.1.255 scope global lxc-nat-bridge
valid_lft forever preferred_lft forever
inet6 fe80::18c1:4ff:fefd:8143/64 scope link
valid_lft forever preferred_lft forever
Nous avons donc maintenant un genre de réseau interne qui est 192.168.1.0/24, celui ci sera le réseau utilisé par nos LXC.
Sur notre serveur, l’interface qui donne sur le net est eno1 (vous aurez peut-être un nom différent tel que eth0). Afin de donner un accès internet aux machines de notre réseau interne, il faut créer une règle masquerade qui va permettre à notre réseau interne de communiquer via l’interface qui donne sur le net. Ceci peut-être réalisé à l’aide de la commande suivante (pensez à remplacer l’interface eno1 par la votre) :
iptables -t nat -A POSTROUTING -s 192.168.1.0/24 -o eno1 -j MASQUERADE
Afin d’autoriser les LXC à créer des interfaces sur la machine, créez le fichier /etc/lxc/lxc-usernet et ajoutez dedans :
forum veth lxc-nat-bridge 1
bdd veth lxc-nat-bridge 1
Nous allons devoir créer un fichier de configuration par defaut qui sera utilisé lors de la création des containers. Ce fichier spécifiera entre autre les uid et gid à utiliser, ainsi que la configuration réseau.
Commencez par executer ces commandes :
su -l forum #Permet de passer de l'utilisateur root à l'utilisateur forum
mkdir -p .config/lxc #Permet de créer les dossiers de base où sera le fichier de config
Créez maintenant .config/lxc/default.conf avec votre editeur préféré (vim, nano, …) et mettez y :
lxc.network.type = veth
lxc.network.link = lxc-nat-bridge
lxc.network.flags = up
lxc.id_map = u 0 100000 65536
lxc.id_map = g 0 100000 65536
lxc.network.ipv4 = 192.168.1.2/24
lxc.network.ipv4.gateway = 192.168.1.1
Pensez bien à utiliser les ids notés précédement ! Pour l’utilisateur forum nous avions noté 100000.
On utilise l’adresse IP 192.168.1.2 car 192.168.1.1 est l’adresse utilisée par le bridge, c’est notre gateway.
Faites un exit pour retourner sur le compte root et répétez l’opération pour l’utilisateur bdd (depuis le su -l) et donc en remplacant 100000 par 165536. Pensez également à utiliser une adresse IP differente, donc 192.168.1.3 en toute logique.
Depuis l’utilisateur forum, il vous suffit d’utiliser la commande suivante afin de le créer :
lxc-create -t download -n forum_container
Un assistant d’installation va se lancer afin que vous puissiez choisir quelle distribution utiliser, quelle version, et quelle architecture utiliser.
Si comme moi vous souhaitez un Debian 9 64bits, je vous invite à utiliser cette commande à la place :
lxc-create -t download -n forum_container -- -d debian -r stretch -a amd64
Après un court instant, le container devrait être créé.
Afin d’avoir un accès internet dans le container, éditez ~/.local/share/lxc/forum_container/rootfs/etc/resolv.conf pour remplacer l’adresse du nameserver par une adresse qui pointe sur un DNS valide. Si vous n’en avez pas, je vous recommande d’utiliser un DNS de ce site : https://www.opennic.org (plutot que d’utiliser les DNS démoniaques de Google, le fameux 8.8.8.8).
Félicitations, votre container vient d’être correctement configuré ! Vous n’avez plus qu’à répéter l’opération avec l’utilisateur bdd.
A ce stade, vous devriez être en mesure d’utiliser vos containers normalement comme n’importe quel système.
Lancez maintenant votre container :
lxc-start -n forum_container -o /tmp/logs -l DEBUG
Les paramètres -o et -l permettent de logger la sortie du lxc-start afin de résoudre un potentiel problème qui pourrait empêcher le démarrage du container. Donc si votre container ne se lance pas, allez voir dans le fichier /tmp/logs.
Si tout s’est bien passé, vous ne devriez avoir aucune sortie, comme si rien ne s’était passé.
Il vous suffit ensuite de rejoindre ce container à l’aide de la commande suivante :
lxc-attach -n forum_container --clear-env
Afin de vérifier que vous avez bien un accès à internet, mettez à jour les repos :
apt update
Vous n’avez pas d’autre moyen de tester votre accès internet car les utilitaires classiques tels que ping, wget, ou encore curl, ne sont pas installés par défaut. Si vous n’avez toujours pas internet, retournez en arrière et verifiez que vous avez correctement tout configuré.
Il se pourrait que votre PATH ne soit pas correctement défini, comme ca a été le cas pour moi. Le paramètre –clear-env est normalement là pour ca, mais si par hasard ca ne fonctionne pas ou que vous avez simplement oublié de la faire, je vous propose donc d’effectuer la commande suivante afin de ne pas être ennuyé par apt qui risque de vous dire qu’il ne trouve pas certains programmes :
export PATH=$PATH:/usr/local/sbin:/usr/sbin:/sbin
Vous devriez maintenant être en mesure d’installer n’importe quel programme à l’interieur de votre container, testez donc d’installer l’utilitaire ping :
apt install inetutils-ping
Répétez les opérations précédentes avec l’utilisateur bdd.
Afin de vérifier que les containers sont bien en mesure de communiquer entre eux, je vous invite à effectuer un ping depuis un container vers l’autre.
En supposant que vous êtes dans le forum_container et que l’adresse IP du bdd_container est 192.168.1.3, effectuez la commande suivante :
root@forum_container:/# ping 192.168.1.3
PING 192.168.1.3 (192.168.1.3): 56 data bytes
64 bytes from 192.168.1.3: icmp_seq=0 ttl=64 time=0.075 ms
64 bytes from 192.168.1.3: icmp_seq=1 ttl=64 time=0.070 ms
64 bytes from 192.168.1.3: icmp_seq=2 ttl=64 time=0.346 ms
Petite astuce : si à l’avenir vous n’avez pas spécialement envie de mettre à jour à la main tout vos containers, vous pouvez activer les mises à jour automatiques en installant le paquet suivant dans vos containers :
apt install unattended-upgrades
Dans ce chapitre, nous allons mettre en place un reverse proxy sur le système hote. Celui-ci sera chargé de chiffrer les communications avec le navigateur des clients et de rediriger les requetes sur le service approprié.
Si par exemple un client se connecte sur notre serveur en ayant rentré dans son navigateur forum.domaine.fr, il faudra transférer les reqûetes à notre LXC forum_container. On pourrait très bien imaginer que d’autres services tournent sur notre serveur, le job du reverse proxy sera donc de créer une correspondance entre un sous-domaine et un LXC.
Commencez par installer nginx sur le serveur hote (pas dans un LXC donc) en root :
apt install nginx
Dans cette partie, je vous propose une configuration simple mais efficace pour rediriger un domaine ou sous-domaine au LXC correspondant.
Remplacez le contenu de votre /etc/nginx/nginx.conf par le suivant :
user www-data;
worker_processes auto;
pid /run/nginx.pid;
include /etc/nginx/modules-enabled/*.conf;
events {
worker_connections 768;
# multi_accept on;
}
http {
##
# Configuration de base
##
sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 65;
types_hash_max_size 2048;
server_name_in_redirect on;
include /etc/nginx/mime.types;
default_type application/octet-stream;
##
# Configuration SSL
##
#ssl_certificate /path_to/fullchain.pem;
#ssl_certificate_key /path_to/privkey.pem;
#ssl_protocols TLSv1 TLSv1.1 TLSv1.2; # Dropping SSLv3, ref: POODLE
#ssl_prefer_server_ciphers on;
#ssl_dhparam /path_to/dhparams.pem;
##
# Configuration des logs
##
access_log /var/log/nginx/access.log;
error_log /var/log/nginx/error.log;
##
# Gzip Settings
##
gzip off; # Ne surtout pas mettre à on -> BREACH vuln
gzip_disable "msie6";
# gzip_vary on;
# gzip_proxied any;
# gzip_comp_level 6;
# gzip_buffers 16 8k;
# gzip_http_version 1.1;
# gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;
##
# Virtual Host Configs
##
include /etc/nginx/conf.d/*.conf;
include /etc/nginx/sites-enabled/*;
#Redirect http --> https
# server {
# listen 80;
# server_name domaine.fr *.domaine.fr;
# return 301 https://$host$request_uri;
# }
server {
# listen 443;
# ssl on;
listen 80;
server_name domaine.fr www.domaine.fr forum.domaine.fr;
add_header Strict-Transport-Security "max-age=31536000";
add_header X-XSS-Protection "1; mode=block";
add_header X-Content-Type-Options nosniff;
location / {
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_pass http://192.168.1.2;
}
}
# Vous pouvez ajouter d'autres machines très simplement !
# server {
# listen 443;
# ssl on;
# add_header Strict-Transport-Security "max-age=31536000";
# add_header X-XSS-Protection "1; mode=block";
# add_header X-Content-Type-Options nosniff;
# server_name wiki.domaine.fr;
# location / {
# proxy_set_header Host $host;
# proxy_set_header X-Real-IP $remote_addr;
# proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
# proxy_set_header X-Forwarded-Proto $scheme;
# proxy_pass http://192.168.1.X;
# }
# }
}
Pensez bien sûr à remplacer tous les domaine.fr par votre nom à vous puis redémarrez ensuite NGINX :
service nginx restart
Cette partie est facultative si vous possédez déjà vos propres certificats, nous allons ici utiliser Let’s Encrypt qui nous permet d’obtenir des certificats gratuitement !
Afin de mettre en place ces certificats, nous allons utiliser Certbot qui est un utilitaire qui va générer les certificats.
Commencez par installer celui-ci :
apt install certbot
Puis générez les certifs en suivant les instructions de certbot :
certbot certonly --authenticator standalone
Regardez dans le dossier /etc/letsencrypt/live, vous devriez avoir un dossier portant le nom de votre domaine.
Nous allons maintenant générer un certificat Diffie Hellman pour l’échange de clés :
openssl dhparam -out /etc/letsencrypt/live/domaine.fr/dhparams.pem 2048
Je considère que générer une clé de 2048 bits est le minimum vu la puissance de calcul des ordinateurs actuels. Mais si vous avez du temps OU une machine très performante, je vous invite à faire une clé de 4096 bits à la place.
Cette partie concerne encore une fois uniquement les certificats Let’s Encrypt.
Testez que la regénération des certificats est bien fonctionnelle à l’aide de la commande :
certbot renew --dry-run
Si tout s’est bien passé, vous pouvez continuer en modifiant le cron certbot accessible à /etc/cron.d/certbot. Par défaut vous trouverez une ligne telle que :
0 */12 * * * root test -x /usr/bin/certbot -a \! -d /run/systemd/system && perl -e 'sleep int(rand(3600))' && certbot -q renew
Il va falloir ajouter des hooks après “renew” afin de redémarrer nginx si une regénération des certificats a lieu. A savoir que Certbot ne renouvellera pas les certificats s’il ne sont pas à moins de 30 jours de leur date d’expiration. Completez donc la commande certbot existante de la manière suivante :
certbot -q renew --pre-hook "service nginx stop" --post-hook "service nginx start"
Nous allons dans cette partie modifier légerement la configuration de notre reverse proxy NGINX afin de lui faire utiliser nos certificats.
Editez le fichier /etc/nginx/nginx.conf et modifiez les éléments suivants :
Décommentez toute la configuration de SSL :
ssl_certificate /path_to/fullchain.pem;
ssl_certificate_key /path_to/privkey.pem;
ssl_protocols TLSv1 TLSv1.1 TLSv1.2; # Dropping SSLv3, ref: POODLE
ssl_prefer_server_ciphers on;
ssl_dhparam /path_to/dhparams.pem;
Et remplacez path_to par le chemin d’accès vers vos certifs. Pour Let’s Encrypt c’est très probablement dans /etc/letsencrypt/live/domaine.fr.
Si vous vous demandez pourquoi SSLv3 doit être désactivé, rendez-vous ici (lecture en anglais).
Décommentez ensuite les choses suivantes :
server {
listen 80;
server_name domaine.fr *.domaine.fr;
return 301 https://$host$request_uri;
}
Ceci permet de forcer le passage de http à https à tous les utilisateurs.
Pour finir, dans le bloc server qui se charge de faire la redirection vers 192.168.1.2, utilisez le port 443 au lieu du port 80 et activez SSL :
listen 443;
ssl on;
#listen 80;
Vous pouvez maintenant redémarrer NGINX, tout devrait fonctionner :
service nginx restart
Commencez par rejoindre le LXC de votre utilisateur bdd et redéfinissez le PATH comme vu précédement.
Installez en suite MariaDB (ou autre SGBD) :
apt install mariadb-server mariadb-client
Lancez ensuite la commande suivante :
mysql_secure_installation
Définissez un mot de passe pour le compte root de la base de données et dites oui (Y) tout au long du script.
Nous allons commencer par nous connecter en local au SGBD à l’aide de la commande suivante :
mysql -u root
Une fois connecté, nous allons créer une base de données qui ne sera utilisée que pour le service web correspondant, à savoir : le forum. Lancez donc la commande suivante afin de créer cette base de données :
MariaDB [(none)]> CREATE DATABASE mybb_db;
Query OK, 1 row affected (0.00 sec)
Créez ensuite un utilisateur :
CREATE USER 'mybb_db'@'192.168.1.2' IDENTIFIED BY 'password';
Comme vous pouvez le constater, on a créé un utilisateur nommé mybb_db. Cet utilisateur sera en mesure d’être utilisé uniquement depuis l’adresse IP 192.168.1.2 qui est en fait l’adresse IP du forum_container.
Nous devons ensuite donner les droits à cet utilisateur pour qu’il puisse utiliser la base de données précédement créée :
GRANT ALL PRIVILEGES ON mybb_db.* to 'mybb_db'@'192.168.1.2';
Afin d’autoriser les connexions distantes (au sein du réseau local) au SGBD, modifiez le fichier /etc/mysql/mariadb.conf.d/50-server.cnf et plus précisément :
bind-address = 192.168.1.3
Redémarrez mariadb :
systemctl restart mariadb
Voilà qui est terminé pour la création de la base de données. Il est très fortement recommandé de bien cloisonner les différents utilisateurs et les accès qui leurs sont accordés sur la bdd afin d’éviter qu’un service qui a été piraté soit en mesure d’altérer la base de données d’un autre service.
Dans ce chapitre nous allons installer NGINX comme serveur Web. De plus, le CMS que je vais utiliser (MyBB) necessite PHP, je vais donc me focaliser dans cet article sur l’utilisation de PHP (en version 7) avec NGINX.
Commencez par vous connecter à l’utilisateur forum et à rejoindre le container.
Executez la commande suivante :
apt install nginx php-fpm php-mysql wget unzip
Etant donné que chacun de nos containers n’est utilisé que pour un seul service, on ne va pas se prendre la tête à créer de nouveaux fichiers et bien séparer les configs. Non, on va plutot modifier le fichier de config par défaut, ce sera LE fichier de config du serveur. Ce fichier se trouve en /etc/nginx/sites-available/default
En voici un exemple de configuration que vous pouvez reprendre :
server {
listen 80 default_server;
listen [::]:80 default_server;
root /var/www/html;
index index.php;
server_name www.domaine.fr domaine.fr forum.domaine.fr;
location / {
try_files $uri $uri/ /index.php?$args;
}
location = /favicon.ico {
log_not_found off;
access_log off;
}
location = /robots.txt {
allow all;
log_not_found off;
access_log off;
}
error_page 404 /404.html;
location ~ \.php$ {
try_files $uri =404;
fastcgi_pass unix:/var/run/php/php7.0-fpm.sock;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
fastcgi_param REMOTE_ADDR $http_x_real_ip; # Permet d'utiliser la véritable IP du visiteur et non pas celle du reverse proxy
}
}
Il faut maintenant redémarrer nginx afin d’appliquer les changements de configuration :
systemctl restart nginx
Pour ma part, je vais utiliser myBB qui est un CMS permettant de créer un forum simplement.
Téléchargez l’archive de ce que vous voulez installer, décompressez là et mettez les bons fichiers à la racine de votre serveur web qui est /var/www/html :
cd /tmp && wget https://resources.mybb.com/downloads/mybb_1812.zip
unzip mybb_1812.zip
mv Upload/* /var/www/html/
Vous utilisez très probablement le compte root du LXC, il est donc indispensable de redéfinir le propriétaire des fichiers dans le dossier html/ afin que nginx puisse les utiliser sans soucis :
chown -R www-data:www-data /var/www/html/
www-data étant l’utilisateur par défaut utilisé par nginx.
On peut maintenant se rendre sur notre site afin de lancer l’installation. Je vous épargne les détails de l’installation car ca n’a rien d’exceptionnel et ca n’est pas le sujet principal de cet article.
Je tiens tout de même à vous montrer la configuration de la connexion à la base de donnée :
Comme vous pouvez le constater on rentre l’adresse du LXC utilisé pour notre base de données.
Bravo si vous êtes arrivés au bout de cet article. Comme moi, cela a dû vous prendre un bon bout de temps. Vous avez maintenant une base propre et sécurisée grace à l’isolation que nous fournit LXC. Si vous avez des critiques, remarques, suggestions sur l’article, n’hesitez pas à poster un commentaire ci-dessous.